迷宫最短路径问题(ShortestPath)的求解——利用链式队列
注:借助于栈求解迷宫问题时,并不能保证找到一条从迷宫入口到迷宫出口的最短路径。而借助于队列,可以找到从迷宫入口到迷宫出口的最短路径(如果有的话)。在迷宫中寻找最短路径问题在其他领域也存在,例如,在解决电路布线问题时,一种很常用的方法是在布线区域叠上一个网格,该网格把布线区域划分成n*m个方格,就像迷宫一样。从一个方格a的中心点连接到另一个方格b的中心点时,转弯处必须采取直角,如果已经有某条线路经过一个方格,则封锁该方格。希望使用a和b之间的最短路径来作为布线的路径,以便减少信号的延迟。
1. 迷宫问题的提法
- 迷宫问题是典型的图的搜索问题。
- 假设一个迷宫,只有一个入口和一个出口。如果从迷宫的入口到达出口,途中不出现行进方向错误,则得到一条最佳路线。
- 为此,用一个二维数组maze[m][n]来表示迷宫。
(1)当数组元素maze[i][j]=1 (0≤i≤m-1,1≤j≤n-1),表示该位置是墙壁,不能通行。
(2)当数组元素maze[i][j]=0 (0≤i≤m-1,1≤j≤n-1),表示该位置是通路,可以通行。 - 注:数组的第0行、第m-1行,第0列、第n-1列,必须是迷宫的围墙,即上述行列的所有坐标对应的数值必须为1(除了入口和出口两个位置的坐标对应的数值可以为0以外),不能通行。
2. 利用队列求解最短路径的算法原理
- 先从位置a开始搜索,把从a可到达的相邻方格都标记为1(表示与a的距离为1)。
- 然后把从标记为1的方格可到达的相邻方格都标记为2(表示与a的距离为2)。
- 如此继续标记下去,直到到达位置b或者找不到可到达的相邻方格为止。
- 按照上述搜索过程,当最后到达b时,就可以在b上读出b与a之间的距离。
- 为了得到a和b之间的最短路径,从b开始,首先移动到一个比b的标号小的相邻位置上,一定存在这样的相邻位置,因为任一个方格上的标号与它相邻方格上的编号都相差1。
- 设任一时刻在迷宫中的位置[i][j]标记为X,X周围有4个前进方向,它实际是一系列交通路口,如果某一方向是0值,表示该方向有路可通,反之表示该方向已堵死。
- 为了有效地选择下一位置,可以将从位置[i][j]出发可能的前进方向预先定义在一个表内,按顺时针方向为Right([i][j+1]),Down([i+1][j]),Left([i][j-1]),Up([i-1][j])。
- (1)前进方向示意图:
(2)前进方向表:
Move[q].dir move[q].a move[q].b “N” -1 0 “E” 0 1 “S” 1 0 “W” 0 -1
3. 利用队列求解最短路径
3.1 链式队列的类定义及其操作的实现
文件:LinkedQueue.h
#ifndef LINKED_QUEUE_H_ #define LINKED_QUEUE_H_ #include <iostream> using namespace std; template <class T> struct LinkNode //链表结点类的定义 { T data; //数据域 LinkNode<T> *link; //指针域——后继指针 //仅初始化指针成员的构造函数 LinkNode(LinkNode<T>* ptr = NULL){ link = ptr; } //初始化数据与指针成员的构造函数 LinkNode(const T& value, LinkNode<T>* ptr = NULL){ data = value; link = ptr; } }; template <class T> class LinkedQueue { public: LinkedQueue(); //构造函数 ~LinkedQueue(); //析构函数 public: LinkNode<T>* getHead() const; //获取队头结点 bool EnQueue(const T& x); //新元素x入队 bool DeQueue(T& x); //队头元素出队,并将该元素的值保存至x bool IsEmpty() const; //判断队列是否为空 void MakeEmpty(); //清空队列的内容 private: LinkNode<T> *front; //队头指针,即链头指针 LinkNode<T> *rear; //队尾指针,即链尾指针 }; //构造函数 template <class T> LinkedQueue<T>::LinkedQueue() : front(NULL), rear(NULL) { cout << "$ 执行构造函数" << endl; } //析构函数 template <class T> LinkedQueue<T>::~LinkedQueue() { cout << "$ 执行析构函数" << endl; MakeEmpty(); } //获取队头结点 template <class T> LinkNode<T>* LinkedQueue<T>::getHead() const { return front; } //新元素x入队 template <class T> bool LinkedQueue<T>::EnQueue(const T& x) { LinkNode<T> *newNode = new LinkNode<T>(x); if (NULL == newNode) { return false; } if (NULL == front) { front = newNode; rear = newNode; } else { rear->link = newNode; rear = rear->link; } return true; } //队头元素出队,并将该元素的值保存至x template <class T> bool LinkedQueue<T>::DeQueue(T& x) { if (true == IsEmpty()) { return false; } LinkNode<T> *curNode = front; front = front->link; x = curNode->data; delete curNode; return true; } //判断队列是否为空 template <class T> bool LinkedQueue<T>::IsEmpty() const { return (NULL == front) ? true : false; } //清空队列的内容 template <class T> void LinkedQueue<T>::MakeEmpty() { LinkNode<T> *curNode = NULL; while (NULL != front) //当链表不为空时,删去链表中所有结点 { curNode = front; //保存被删结点 front = curNode->link; //被删结点的下一个结点成为头结点 delete curNode; //从链表上摘下被删结点 } } #endif /* LINKED_QUEUE_H_ */
3.2 迷宫的初始化及前进方向表的定义
文件:MazeConfig.h
#ifndef MAZECONFIG_H_ #define MAZECONFIG_H_ #include <iostream> #include <windows.h> using namespace std; //位置坐标和前进方向序号的三元组结构定义 struct items { int x;//位置的x坐标 int y;//位置的y坐标 }; //前进方向表的结构定义 struct offfsets { int a;//x方向的偏移 int b;//y方向的偏移 char *dir;//移动的方向描述 }; const int m = 9;//迷宫的行数 const int n = 9;//迷宫的列数 const int dir_count = 4;//前进方向的总数 const int pathmark = -1;//迷宫通路的标识值 items entry = { 3, 2 };//迷宫入口网格坐标 items exitus = { 4, 6 };//迷宫出口网格坐标 //各个方向的偏移表定义 offfsets moves[dir_count] = { { 0, 1, "Right" }, { 1, 0, "Down" }, { 0, -1, "Left" }, { -1, 0, "Up" } }; //初始化迷宫 int Maze[m][n] = { { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1 }, { 1, 0, 0, 1, 1, 0, 0, 0, 1 }, { 1, 0, 0, 0, 0, 1, 0, 0, 1 }, { 1, 0, 0, 0, 1, 1, 0, 0, 1 }, { 1, 1, 0, 0, 0, 1, 0, 0, 1 }, { 1, 1, 1, 1, 0, 0, 0, 0, 1 }, { 1, 1, 1, 1, 0, 0, 0, 0, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1 } }; //打印迷宫 void print_maze() { cout << "======>MazePath" << endl; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (Maze[i][j] == pathmark) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); } else { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); } cout.width(4); //设置字段宽度为n位 cout << Maze[i][j]; } cout << endl; } SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); } #endif /* MAZECONFIG_H_ */
3.3 最短路径的求解算法实现
文件:SeekShortestPath.h
#ifndef SEEKPATH_SHORTEST_H_ #define SEEKPATH_SHORTEST_H_ #include "MazeConfig.h" #include "LinkedQueue.h" void MarkPath() { int PathLen = Maze[exitus.x][exitus.y]; cout << "迷宫最短路径长度:" << PathLen << endl; items *path = new items[PathLen]; items cur = exitus; items tmp, next; for (int j = PathLen - 1; j >= 0; j--) { tmp = path[j] = cur; Maze[tmp.x][tmp.y] = pathmark; for (int i = 0; i < dir_count; i++) { next.x = cur.x + moves[i].a; next.y = cur.y + moves[i].b; if (Maze[next.x][next.y] == j) { break; } } cur = next; } } bool SeekShortestPath() { LinkedQueue<items>* linkedQueue = new LinkedQueue<items>; items cur = entry; items next = entry; Maze[entry.x][entry.y] = 1; while ((next.x != exitus.x) || (next.y != exitus.y)) { for (int d = 0; d < dir_count; d++) { next.x = cur.x + moves[d].a; next.y = cur.y + moves[d].b; if (Maze[next.x][next.y] == 0) { Maze[next.x][next.y] = Maze[cur.x][cur.y] + 1; if ((next.x == exitus.x) && (next.y == exitus.y)) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "======>SeekShortestPath Success" << endl; break; } linkedQueue->EnQueue(next); } } if (true == linkedQueue->IsEmpty()) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "======>SeekPath Fail" << endl; delete linkedQueue; return false; } linkedQueue->DeQueue(cur); } MarkPath(); delete linkedQueue; return true; } #endif /* SEEKPATH_SHORTEST_H_ */
3.4 主函数(main函数)的实现
文件:main.cpp
#include "SeekShortestPath.h" int main(int argc, char* argv[]) { print_maze(); SeekShortestPath(); print_maze(); system("pause"); return 0; }
3.5 迷宫问题求解结果
- 控制台输出,迷宫通路是绿色高亮显示的路径。
参考文献:
[1]《数据结构(用面向对象方法与C++语言描述)(第2版)》殷人昆——第三章
[2] 百度搜索关键字:迷宫问题、队列、广度优先搜索算法